/*
 * Estacionamiento.cpp
 *
 *  Created on: 04/10/2012
 */

#include "Estacionamiento.h"
#include "Entrada.h"
#include "Salida.h"

Estacionamiento::Estacionamiento(int numeroEstacionamiento):
	numeroEstacionamiento(numeroEstacionamiento),
	entradasAbiertas (true),
	salidasAbiertas (true),
	colaAdministrador (Cola<MensajeTicket>((char*) ARCHIVO_COLA_ADMINISTRADOR, 1)),
	colaEstacionamiento(Cola<MensajeTicket>((char*)ARCHIVO_COLAS_ESTACIONAMIENTOS, numeroEstacionamiento)),
	cerrarSalidasPendiente (false){

	SignalHandler::getInstance()->registrarHandler(SIGUSR1, this);
	SignalHandler::getInstance()->registrarHandler(SIGUSR2, this);
	SignalHandler::getInstance()->registrarHandler(SIGINT, this);
	SignalHandler::getInstance()->registrarHandler(SIGTERM, this);

	Log::getInstance()->loguear("Estacionamiento " + intToString(this->numeroEstacionamiento) + ": creado.");
}

Estacionamiento::~Estacionamiento() {
	SignalHandler::getInstance()->removerHandler(SIGUSR1);
	SignalHandler::getInstance()->removerHandler(SIGUSR2);
	SignalHandler::getInstance()->removerHandler(SIGINT);
	SignalHandler::getInstance()->removerHandler(SIGTERM);
	SignalHandler::destruir();
}

/**
 * Crea las entradas y salidas del estacionamiento.
 */
int Estacionamiento::inicializar(){
	if(this->crearEntradas() == 0){
		if (this->crearSalidas() == 0){
			// Proceso estacionamiento
			return 0;
		}
	}
	return 1;
}

/**
 * Destruye la cola del estacionamiento y la cola que usan las salidas.
 */
void Estacionamiento::destruir(){
	// Cierro la cola de las salidas del estacionamiento
	Cola<MensajeTicket> colaSalidas = Cola<MensajeTicket>((char*)ARCHIVO_COLAS_SALIDAS, this->numeroEstacionamiento);
	colaSalidas.destruir();

	// Cierro la cola del estacionamiento
	this->colaEstacionamiento.destruir();

	Log::getInstance()->loguear("Estacionamiento " + intToString(this->numeroEstacionamiento) + ": destruido.");
}

/**
 * Crea los procesos de las entradas.
 */
int Estacionamiento::crearEntradas(){
	for (int i = 0; i < CANTIDAD_ENTRADAS; i++){
		pid_t pid = fork();
		if (pid == 0){
			Entrada entrada(this->numeroEstacionamiento, i);
			entrada.recibirAutos();
			return 1;
		} else {
			this->agregarPidEntrada(pid);
		}
	}
	// Solo el proceso Estacionamiento devuelve 0
	return 0;
};

/**
 * Crea los procesos de las salidas.
 */
int Estacionamiento::crearSalidas(){
	for (int i = 0; i < CANTIDAD_SALIDAS; i++){
		pid_t pid = fork();
		if (pid == 0){
			Salida salida(this->numeroEstacionamiento, i);
			salida.sacarAutos();
			return 1;
		} else {
			this->agregarPidSalida(pid);
		}
	}
	// Solo el proceso Estacionamiento devuelve 0
	return 0;
}

/**
 * Handler de señales:
 * SIGUSR1: Señal que envia el administrador para cerrar las entradas.
 * SIGUSR2: Señal que envia el administrador para cerrar las salidas.
 * SIGINT: Se ignora. El terminador la toma para iniciar la detención del programa ordenadamente.
 * SIGTERM: Se ignora.  El terminador la toma para iniciar la detención del programa ordenadamente.
 */
int Estacionamiento::handleSignal(int signum){
	if (signum == SIGUSR1){
		Log::getInstance()->loguear("Estacionamiento " + intToString(this->numeroEstacionamiento)
			+ ": me llego SIGUSR1. Cierro las entradas.");
		this->cerrarEntradas();

	} else if (signum == SIGUSR2){
		Log::getInstance()->loguear("Estacionamiento " + intToString(this->numeroEstacionamiento)
			+ ": me llego SIGUSR2. Cierro las salidas.");
		if (this->entradasAbiertas){
			this->cerrarSalidasPendiente = true;
		} else {
			this->cerrarSalidas();
		}

	} else if (signum == SIGINT){
		Log::getInstance()->loguear("Estacionamiento " + intToString(this->numeroEstacionamiento)
			+ ": me llego SIGINT. No hago nada.");
		// La señal la toma el terminador para detener la ejecución ordenadamente.

	} else if (signum == SIGTERM){
		Log::getInstance()->loguear("Estacionamiento " + intToString(this->numeroEstacionamiento)
			+ ": me llego SIGTERM. No hago nada.");
		// La señal la toma el terminador para detener la ejecución ordenadamente.

	}
	return 0;
}

/**
 * Metodo que se ejecuta continuamente mientras el estacionamiento esté abierto.
 */
void Estacionamiento::funcionar(){
	while(this->entradasAbiertas || this->salidasAbiertas){
		this->leerColaEstacionamiento();
	}

	Log::getInstance()->imprimir("Estacionamiento " + intToString(this->numeroEstacionamiento)
		+ ": se cierra el estacionamiento.");
}

/**
 * Lee la cola del estacionamiento. Recibe dos tipos de mensajes:
 * SALIDA_LIBERA_LUGAR: Una salida marca un lugar como desocupado.
 * ENTRADA_PIDE_LUGAR_P: Una entrada pide un lugar disponible para un auto recién llegado.
 */
void Estacionamiento::leerColaEstacionamiento(){
	MensajeTicket mensaje;
	int resultado = this->colaEstacionamiento.leer(-1 * ENTRADA_PIDE_LUGAR_P, &mensaje);
	if (resultado != -1){
		if (mensaje.mtype == SALIDA_LIBERA_LUGAR){
			this->liberarLugar(mensaje.ticket);
		} else if (mensaje.mtype == ENTRADA_PIDE_LUGAR_P){
			this->reservarLugar();
		}
	}
}

/**
 * Metodo que le pide un lugar disponible al administrador.
 *
 * Escribe en la cola del administrador para que el administrador solo lea una cola y no tenga que andar
 * alternando la lectura entre varias.
 * Lee en la cola del estacionamiento, porque si usara la del administrador, en caso de que otro estacionamiento
 * pidiera un lugar al mismo tiempo, puede recibir la información de otro estacionamiento.
 */
void Estacionamiento::reservarLugar(){
	MensajeTicket mensaje;
	mensaje.mtype = ESTACIONAMIENTO_PIDE_LUGAR_P;
	mensaje.ticket.setNumeroEstacionamiento(this->numeroEstacionamiento);

	// Pide un lugar al administrador
	this->colaAdministrador.escribir(mensaje);

	// Lee el mensaje
	int resultado = this->colaEstacionamiento.leer(ESTACIONAMIENTO_PIDE_LUGAR_R , &mensaje);
	if (resultado == -1){
		Log::getInstance()->loguear("Estacionamiento "+ intToString(this->numeroEstacionamiento)
			+ ": Error al leer la cola del estacionamiento al pedir un lugar disponible.");
		// Si hubo errores digo que no hay lugar disponible.
		mensaje.ticket.setLugarOcupado(-1);
	} else {
		Log::getInstance()->loguear("Estacionamiento "+ intToString(this->numeroEstacionamiento)
				+ ": El administrador me cede el lugar " + intToString(mensaje.ticket.getLugarOcupado()) + ".");
	}

	// Le pasa el lugar a la entrada
	mensaje.mtype = ENTRADA_PIDE_LUGAR_R;
	this->colaEstacionamiento.escribir(mensaje);
}

/**
 * Envia un mensaje al administrador informandole el lugar que fue desocupado.
 */
void Estacionamiento::liberarLugar(Ticket ticket){
	Log::getInstance()->loguear("Estacionamiento "+ intToString(this->numeroEstacionamiento)
			+ ": se libera lugar " + intToString(ticket.getLugarOcupado()) + ".");

	MensajeTicket mensaje;
	mensaje.mtype = ESTACIONAMIENTO_LIBERA_LUGAR;
	mensaje.ticket = ticket;
	this->colaAdministrador.escribir(mensaje);
}

/**
 * Envia señales a las entradas para que finalicen la ejecución.
 */
void Estacionamiento::cerrarEntradas(){
	Log::getInstance()->loguear("Estacionamiento: comienzo el envio de señales a entradas.");
	int state;
	for (vector<pid_t>::iterator itEntradas = this->pidsEntradas.begin(); itEntradas != this->pidsEntradas.end() ; itEntradas++){
		kill(*itEntradas, SIGUSR1);
		waitpid(*itEntradas, &state, 0);
	}
	this->entradasAbiertas = false;

	if (this->cerrarSalidasPendiente){
		this->cerrarSalidas();
	}
}

/**
 * Envía señales a las salidas para que finalicen la ejecución.
 */
void Estacionamiento::cerrarSalidas(){
	Log::getInstance()->loguear("Estacionamiento: comienzo el envio de señales a salidas.");
	int state;
	for (vector<pid_t>::iterator itSalidas = this->pidsSalidas.begin(); itSalidas != this->pidsSalidas.end() ; itSalidas++){
		kill(*itSalidas, SIGUSR1);
		waitpid(*itSalidas, &state, 0);
	};

	this->salidasAbiertas = false;
}

int Estacionamiento::getNumeroEstacionamiento(){
	return this->numeroEstacionamiento;
}

void Estacionamiento::agregarPidEntrada(pid_t pidEntrada){
	this->pidsEntradas.push_back(pidEntrada);
}

void Estacionamiento::agregarPidSalida(pid_t pidSalida){
	this->pidsSalidas.push_back(pidSalida);
}

